`timescale 1ns / 1ps

module pwc_L1
#(
    parameter N_IN      = 3,
    parameter N_OUT     = 4,
    parameter N_ICH     = 3,
    parameter N_OCH     = 48,
    parameter BIT_I     = 8,
    parameter BIT_W     = 5,
    parameter BIT_O     = 16,
    parameter ROM_FILE  = "L1_PW.mem",
    parameter ROM_TYPE  = "distributed",
    parameter ROM_LATENCY = 1
)
(
    input   clk,
    input   rst,
    
    input   i_vld,
    output  i_rdy,
    input   [ N_IN*BIT_I-1 : 0]    i_data,
    
    output  o_vld,
    input   o_rdy,
    output  [N_OUT*BIT_O-1 : 0]    o_data
);

localparam DATAPATH_LATENCY = 3 + N_IN;
localparam W_IN  =  N_IN * BIT_I;
localparam W_OUT = N_OUT * BIT_O;
localparam W_WT  = N_OUT * N_IN * BIT_W;
localparam D_WT  = N_OCH * N_ICH / (N_OUT * N_IN);

wire  ii_vld;
wire  [W_IN-1 : 0]  ii_data;
wire  wt_vld;
wire  [W_WT-1 : 0]  wt_data;

wire in_rdy;
wire in_vld;
wire enable;

assign in_vld = ii_vld & wt_vld;
assign in_rdy = in_vld & enable;
assign enable = ~o_vld | o_rdy;

pwc_buffer_L1 inst_inbuf
(
    .clk                     ( clk      ),
    .rst                     ( rst      ),

    .i_vld                   ( i_vld    ),
    .i_data                  ( i_data   ),
    .i_rdy                   ( i_rdy    ),

    .o_rdy                   ( in_rdy   ),
    .o_vld                   ( ii_vld   ),
    .o_data                  ( ii_data  )
);

ROM_Loader
#(
    .WIDTH          (W_WT),
    .DEPTH          (D_WT),
    .MEM_FILE       (ROM_FILE),
    .MEM_TYPE       (ROM_TYPE),
    .MEM_LATENCY    (ROM_LATENCY)
) weight_loader
(
    .clk        (clk),
    .rst        (rst),

    .o_vld      (wt_vld),
    .o_rdy      (in_rdy),
    .o_data     (wt_data)
);

shift_reg_rst 
#(
    .N          (DATAPATH_LATENCY),
    .WIDTH      (1)
) inst_vaild_chain
(
    .clk        (clk),
    .rst        (rst),
    .clken      (enable),
    .i_data     (in_vld),
    .o_data     (o_vld)
);

genvar i;
generate
    for (i = 0; i < N_OUT / 2; i = i + 1)
    begin
        pwc_dspchain #(
            .N_PE   ( N_IN   ),
            .BIT_I  ( BIT_I  ),
            .BIT_W  ( BIT_W  ),
            .BIT_O  ( BIT_O  ),
            .LSHIFT ( 20     )
        ) inst_dspchain
        (
            .clk                     ( clk        ),
            .ce                      ( enable     ),
            .ce_acc                  ( enable     ),
            .ld_acc                  ( 1'b0       ),
            .i_data                  ( ii_data    ),
            .i_weight                ( wt_data[N_IN*2*BIT_W * i + N_IN*2*BIT_W - 1 : N_IN*2*BIT_W * i]  ),
            .o_data                  (  o_data[     2*BIT_O * i +      2*BIT_O - 1 :      2*BIT_O * i]  )
        );
    end
endgenerate

endmodule


module pwc_buffer_L1
#(
    parameter N_IN      = 3,
    parameter N_OUT     = 4,
    parameter N_ICH     = 3,
    parameter N_OCH     = 48,
    parameter BIT_I     = 8
)
(
    input   clk,
    input   rst,
    
    input   i_vld,
    output  i_rdy,
    input   [N_IN*BIT_I-1 : 0]    i_data,
    
    output  o_vld,
    input   o_rdy,
    output  [N_IN*BIT_I-1 : 0]    o_data
);

localparam WIDTH = N_IN * BIT_I;
localparam FOLD_I = N_ICH / N_IN;   // == 1
localparam FOLD_O = N_OCH / N_OUT;  // == 12

localparam S0_IDLE = 2'b01;
localparam S1_WORK = 2'b10;

reg  [WIDTH-1 : 0]  in_buf;
reg  [1 : 0]        cstate;

reg  [1 : 0]        nstate;
reg ii_rdy;
reg oo_vld;
reg buf_en;
reg cnt_en;
reg cnt_clr;
wire cnt_rst;
wire cnt_last;

assign i_rdy = ii_rdy;
assign o_vld = oo_vld;
assign o_data = in_buf;
assign cnt_rst = rst | cnt_clr;

zq_counter #(
    .N  (FOLD_O)
) inst_cnt
(
    .clk    (clk),
    .rst    (cnt_rst),
    .clken  (cnt_en),
    .last   (cnt_last),
    .out    ()
);

always @(posedge clk)
begin
    if (buf_en)
        in_buf <= i_data;
end

always @(posedge clk)
begin
    if (rst)
        cstate <= S0_IDLE;
    else
        cstate <= nstate;
end

always @(*)
begin
    (* full_case *)
    case (cstate)
		S0_IDLE:
        begin
            nstate = i_vld ? S1_WORK : S0_IDLE;
		end
		S1_WORK:
        begin
            if (o_rdy & ~i_vld & cnt_last)
                nstate = S0_IDLE;
            else
                nstate = S1_WORK;
		end
		default:
        begin
			nstate = S0_IDLE;
		end
	endcase
end

always @(*)
begin
    (* full_case *)
    case (cstate)
		S0_IDLE:
        begin
            ii_rdy = 1'b1;
            oo_vld = 1'b0;
            buf_en = i_vld;
            cnt_en = 1'b0;
            cnt_clr = 1'b1;
		end
		S1_WORK:
        begin
            ii_rdy = o_rdy & cnt_last;
            oo_vld = 1'b1;
            buf_en = o_rdy & i_vld & cnt_last;
            cnt_en = o_rdy;
            cnt_clr = 1'b0;
		end
	endcase
end

endmodule
